package io.github.yotta.common.web.advice;

import io.github.yotta.common.api.exception.ServiceException;
import io.github.yotta.common.api.model.Response;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;


/**
 * @author yotta
 * @date 2022/11/7 21:01
 */
@Order(Ordered.LOWEST_PRECEDENCE)
@Slf4j
public abstract class BaseRestExceptionAdvice implements MessageSourceAware {
    @Setter
    @Autowired(required = false)
    private ResponseWrapper responseWrapper = new DefaultResponseWrapper();
    @Getter
    protected MessageSource messageSource;

    /**
     * 参数不合法异常处理
     */
    @ExceptionHandler({IllegalArgumentException.class})
    public ResponseEntity<Object> onException(IllegalArgumentException exception, HttpServletRequest request) {
        log.error("Uri: {}, Illegal Argument error: {};", request.getRequestURI(), exception.getMessage(), exception);
        return toResponseEntity(request, exception.getMessage(),HttpStatus.OK,exception);
    }

    @ExceptionHandler({ServiceException.class})
    public ResponseEntity<Object> onException(ServiceException exception, HttpServletRequest request) {
        log.error("Uri: {}, Server error: {};", request.getRequestURI(), exception.getMessage(), exception);
        return toResponseEntity(request, exception.getMessage(),HttpStatus.OK,exception);
    }

    /**
     * 未知异常处理
     */
    @ExceptionHandler({Exception.class})
    public ResponseEntity<Object> onException(Exception exception, HttpServletRequest request) {
        log.error("Uri: {}, unknown error: {};", request.getRequestURI(), exception.getMessage(), exception);
        return toResponseEntity(request, "unknown error",HttpStatus.OK,exception);

    }


    /**
     * 其他异常处理，比如405异常
     *
     * @param ex
     * @param request
     * @return
     * @throws Exception
     */
    @ExceptionHandler({
            HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class,
            HttpMediaTypeNotAcceptableException.class,
            MissingPathVariableException.class,
            MissingServletRequestParameterException.class,
            ServletRequestBindingException.class,
            ConversionNotSupportedException.class,
            TypeMismatchException.class,
            HttpMessageNotReadableException.class,
            HttpMessageNotWritableException.class,
            MethodArgumentNotValidException.class,
            MissingServletRequestPartException.class,
            BindException.class,
            NoHandlerFoundException.class,
            AsyncRequestTimeoutException.class
    })

    public ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
        HttpHeaders headers = new HttpHeaders();
        HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
        if (ex instanceof HttpRequestMethodNotSupportedException) {
            status = HttpStatus.METHOD_NOT_ALLOWED;
        } else if (ex instanceof HttpMediaTypeNotSupportedException) {
            status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
        } else if (ex instanceof HttpMediaTypeNotAcceptableException) {
            status = HttpStatus.NOT_ACCEPTABLE;
        } else if (ex instanceof MissingPathVariableException) {
            status = HttpStatus.INTERNAL_SERVER_ERROR;
        } else if (ex instanceof MissingServletRequestParameterException) {
            status = HttpStatus.BAD_REQUEST;
        } else if (ex instanceof ServletRequestBindingException) {
            status = HttpStatus.BAD_REQUEST;
        } else if (ex instanceof ConversionNotSupportedException) {
            status = HttpStatus.INTERNAL_SERVER_ERROR;
        } else if (ex instanceof TypeMismatchException) {
            status = HttpStatus.BAD_REQUEST;
        } else if (ex instanceof HttpMessageNotReadableException) {
            status = HttpStatus.BAD_REQUEST;
        } else if (ex instanceof HttpMessageNotWritableException) {
            status = HttpStatus.INTERNAL_SERVER_ERROR;
        } else if (ex instanceof MethodArgumentNotValidException) {
            status = HttpStatus.BAD_REQUEST;
        } else if (ex instanceof MissingServletRequestPartException) {
            status = HttpStatus.BAD_REQUEST;
        } else if (ex instanceof BindException) {
            status = HttpStatus.BAD_REQUEST;
        } else if (ex instanceof NoHandlerFoundException) {
            status = HttpStatus.NOT_FOUND;
        } else if (ex instanceof AsyncRequestTimeoutException) {
            status = HttpStatus.SERVICE_UNAVAILABLE;
        }
        log.error(((ServletWebRequest) request).getRequest().getRequestURI() + " " + ex.getMessage(), ex);
        return toResponseEntity(((ServletWebRequest) request).getRequest(), status.getReasonPhrase(), status, ex);
    }



    private ResponseEntity<Object> toResponseEntity(HttpServletRequest request, String message, HttpStatus status,Exception ex) {
        String error = this.messageSource.getMessage(message, null, message, request.getLocale());
        return new ResponseEntity<>(responseWrapper.wrapperErrorResponse(error, status, ex),status);
    }


    @Override
    public void setMessageSource(MessageSource messageSource) {
        this.messageSource = messageSource;
    }
}
